#ifndef NIGHT_TENSOR_EXPR
#define NIGHT_TENSOR_EXPR

#include "core.hpp"
#include "shape.hpp"

namespace night {

namespace tensor {

template< typename S > struct expr_shape;

template< typename Type, typename Tail >
struct expr_shape< shape<Type,Tail> > {

	typedef typename detail::if_c < 
		find< Type, Tail >::value, 
		typename expr_shape< typename erase< Type, Tail >::type >::type,
		typename join< Type, typename expr_shape< Tail >::type >::type
	>::type type;
};

template<> struct expr_shape< shape<> > { typedef shape<> type; };


template< typename S > struct expr_index;

template< typename Type, typename Tail >
struct expr_index< shape<Type,Tail> > {

	typedef typename detail::if_c <
		find< Type, Tail >::value,
		typename join< Type, typename expr_index< typename erase< Type, Tail >::type >::type >::type,
		typename expr_index< Tail >::type
	>::type type;
};

template<> struct expr_index< shape<> > { typedef shape<> type; };


template< typename E, typename R, typename S > 
struct Expr : detail::if_c<
		detail::is_same< typename expr_index<S>::type, shape<> >::value,
		Base <E,R,S>,
		Base< Expr<E,R,S>, typename detail::remove_cref<R>::type, typename expr_shape<S>::type >
	>::type
{

	typedef typename Expr::result_type value_type;

	template< typename LE, typename RE >
	struct AddAssign {
		typename LE::value_type lhs;
		typename RE::value_type rhs;

		AddAssign ( typename LE::param_type x, typename RE::param_type y ) : lhs (x), rhs (y) {}

		template< typename Shape >
		void operator () ( Shape const & s ) const { this->lhs += this->rhs.at (s); }
	};

	template< typename Shape >
	value_type at ( Shape const & s ) const { 
		value_type sum = 0;

		for_each< typename expr_index<S>::type >::apply ( 
			s, AddAssign< by_ref<value_type>, by_cref<E> > ( sum, derived<E> (*this) ) );

		return sum; 
	}
};

} // end namespace night::tensor

} // end namespace nigth

#endif
